package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 用户验证身份自定义注解增强切面类
 * @author Mahone
 */

@Aspect
@Component
@Slf4j
public class LoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 环绕通知
     * @param proceedingJoinPoint 目标方法对象
     * @param guiGuLogin 执行切面方法的注解
     * @return 目标方法执行结果
     * @throws Throwable
     */
    @Around("@annotation(guiGuLogin)")
    public Object authAround(ProceedingJoinPoint proceedingJoinPoint,GuiGuLogin guiGuLogin) throws Throwable{
        // 前置同通知
        log.info("执行前置通知");
        // 1.获取请求对象HttpServletRequest
         // 1.1 从请求上下文中获取RequestAttributes接口-从ThreadLocal<RequestAttributes>中获取到
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 1.2 将RequestAttributes接口转为实现类ServletRequestAttributes
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
         // 1.3 获取请求对象
        HttpServletRequest request = servletRequestAttributes.getRequest();
        // 1.4 获取小程序端token令牌
        String token = request.getHeader("token");

        // 2.验证token令牌
         // 2.1 拼接用户登录key 形式：user:login:token令牌  登录存入Value：UserInfoVo对象
        String tokenKey =  RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(tokenKey);

        // 3.token验证失败，要求用户登录方可访问，抛出异常：业务代码：208，小程序引导用户登录
        if (guiGuLogin.require() && userInfoVo == null){
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        // 4.如果用户登录，将用户ID存入ThreadLocal 方便后续控制层、业务层、持久层都可以使用登录用户ID
        if (userInfoVo != null){
            AuthContextHolder.setUserId(userInfoVo.getId());
        }
        Object proceed;
        try {
            // 执行目标方法(所有加了guiGuLogin注解的方法)
            proceed = proceedingJoinPoint.proceed();
        } finally {
            // 后置通知
            log.info("执行后置通知");
            // 5.清理ThreadLocal中用户信息 避免内存泄漏
            AuthContextHolder.removeUserId();
        }
        return proceed;

    }

}
